// 实现日志

// 1. 日志策略
// 2. 日志表示

// 日志 -- 1 . 写入日志  2 . 刷新
#pragma once

#include <iostream>
#include <string>
#include <sys/types.h>
#include <unistd.h>
#include <time.h>
#include <memory>
// C++文件操作
#include <fstream>
#include <filesystem>
// C++文件自动识别合并
#include <sstream>

// 加入锁
#include "Mutex.hpp"

namespace LogModel
{
    using namespace MutexModel;

    // 2 . 实现日志的刷新策略
    //     日志的刷新有两种 : 1. 刷新到显示器  2 . 刷新到文件
    //     所以 , 实现多态 , 具体策略指向基类 , 基类调用
    // 实现基类 - 日志策略 , 其余继承 , 实现多态的日志刷新
    class LogStrategy
    {
    public:
        LogStrategy() {}
        ~LogStrategy() {}

        // 基类的刷新策略 , 继承和多态实现的虚函数
        virtual void SyncLog(const std::string &message) = 0; // 不用具体实现 , 后面继承
    };

    // 刷新策略 , 会有很多线程可能都会访问刷新
    // 1. 显示器刷新
    class ConsoleLogStrategy : public LogStrategy
    {
    public:
        ConsoleLogStrategy() {}
        ~ConsoleLogStrategy() {}

        // 重写基类的虚函数
        void SyncLog(const std::string &message) override // 检查是否重写
        {
            // 防止出现消息混乱 ,数据不一致
            LockGuard lockguard(_mutex);
            std::cout << message << std::endl;
        }

    private:
        Mutex _mutex;
    };

    // 默认文件路径
    const std::string default_path = "./log";
    // 默认文件名
    const std::string default_file = "Logfile.log";
    //默认的换行符 
    const std::string endl = "\r\n"; 
    // 2. 文件刷新
    class FileLogStrategy : public LogStrategy
    {
    public:
        // 文件 , 所以要创文件 , 打开文件 , 写日志内容
        FileLogStrategy()
            : _path(default_path), _file(default_file)
        {
            // 创建文件
            // 1. 判断文件是否存在 , 存在不创建
            if (std::filesystem::exists(_path))
                return;
            // 2. 不存在 , 创建之
            try
            {
                // 创建一个指定文件路径
                std::filesystem::create_directories(_path);
            }
            catch (const std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << std::endl;
            }
        }
        ~FileLogStrategy() {}

        // 重写基类的虚函数
        void SyncLog(const std::string &message) override
        {

            // 1 . 形成文件路径  "./log/Logfile.log"
            std::string path = (_path.back() == '/' ? "" : "/") + _file;
            // 2. C++文件操作
            // 2.1 打开文件
            std::ofstream out(path, std::ios::app); // 追加打开写

            LockGuard lockguard(_mutex);
            {
                if (!out.is_open())
                {
                    return;
                }
                // 2.2 写文件 , 写日志内容
                // 流式写入 --- 用的就是流式接口
                out << message << endl;
            }
            // 2.3 关闭文件
            out.close();
        }

    private:
        std::string _path; // 日志文件所在路径
        std::string _file; // 日志文件本身
        Mutex _mutex;
    };

#if 0   
    //  日志包含 
    //  1. 日志等级
    //  2. 时间戳
    //  3. 进程ID
    //  4. 行号 
    //  5. 源文件名
    //  6. 用户自定义的日志消息

    //实现效果  : 
    [2025-07-15][16:23:50][DEBUG][131231][14][main.cc] - hello world

#endif

    // 1 . 日志等级枚举类
    enum class LogLevel
    {
        DEBUG,   // 调试
        INFO,    // 正常信息
        WARNING, // 警告
        ERROR,   // 错误
        FATAL    // 致命
    };

    // 2 . 日志等级要转成字符串 , 因为是枚举类的整数 , ss不支持
    std::string LogLevelToString(LogLevel &level)
    {
        switch (level)
        {
        case LogLevel::DEBUG:
            return "DEBUG";
        case LogLevel::INFO:
            return "INFO";
        case LogLevel::WARNING:
            return "WARNING";
        case LogLevel::ERROR:
            return "ERROR";
        case LogLevel::FATAL:
            return "FATAL";
        default:
            return nullptr;
        }
    }

    // 3. 时间戳  -- localtime_r
    std::string GetTimeStampe()
    {
        time_t currtime = time(nullptr);
        // 获取内核时间结构体
        struct tm curr_tm;
        localtime_r(&currtime, &curr_tm);

        // 格式化时间
        char timebuffer[128];
        snprintf(timebuffer, sizeof(timebuffer),
                 "%4d-%02d-%02d] [%02d:%02d:%02d",
                 curr_tm.tm_year + 1900, // 因为年是 -1900的 , 所以我们要加上
                 curr_tm.tm_mon + 1,     // 因为月是 0-11 , 所以要+1
                 curr_tm.tm_mday,
                 curr_tm.tm_hour,
                 curr_tm.tm_min,
                 curr_tm.tm_sec);
        return timebuffer;
    }

// 日志主体
#if 0 
        //日志主体类是最后直接暴露给用户的 , 用户使用日志就用 Logger 类
        //所以 , 该类是干什么的 ??? 
                a . 负责让用户选择日志刷新策略 
                b . 选择好后该类可以用LogMessage 类完成日志信息填充

#endif
    class Logger
    {
    public:
        Logger()
        {
            // 默认刷新策略  -- 显示器
            EnableConsoleLogStrategy();
        }
        ~Logger() {}

        // 显示器刷新
        void EnableConsoleLogStrategy()
        {
            _fflush_strategy = std::make_unique<ConsoleLogStrategy>();
        }

        // 文件刷新
        void EnableFileLogStrategy()
        {
            _fflush_strategy = std::make_unique<FileLogStrategy>();
        }

// 我们使用内部类 , 这个类是完成一条日志信息的,因为日志信息需要Logger类中的,所以内部类直接可以访问外部类
#if 0 
            //1. 日志打印,对于用户来说 , 可能会这样写 : 
            logger << "head" << 13 << ad << ... ; 用户可以一直写
            //所以 , 要重载 << 运算符支持用户多组信息
            //                和多种类型的信息
#endif
        class LogMessage
        {
        public:
            LogMessage(LogLevel &level, std::string &filename, int line, Logger &logger)
                : _log_time(GetTimeStampe()), _pid(getpid()), _log_level(level), _line_number(line), _src_filename(filename), _logger(logger)
            {
                // 这里就把用户给的信息处理成日志 -- 用到 sstream
                // 这个支持自动识别类型合并

                // 1 . 完成日志左边信息
                std::stringstream ss;
                ss << "[" << _log_time << "] "
                   << "[" << LogLevelToString(_log_level) << "] "
                   << "[" << _pid << "] "
                   << "[" << _line_number << "] "
                   << "[" << _src_filename << "] "
                   << "- ";
                // 进行上述的合并 , 上述是左边信息 , 先合并到 _log_info
                _log_info = ss.str(); // 自动合并成一个整条字符串
            }
            ~LogMessage()
            {
                // 刷新日志信息
                if (_logger._fflush_strategy)
                {
                    // 实现多态 -- 基类的指针调用
                    _logger._fflush_strategy->SyncLog(_log_info);
                }
            }

            // 2. 处理用户自定义的日志信息 -- 完成日志右边信息
            // Logger << "hea" <<  123 .. ---- > 重载 <<
            // 用户的信息可以是多种类型的 , 所以用模版实现
            template <class T>
            LogMessage& operator<<(const T &info)
            {
                std::stringstream ss;
                ss << info;            // 这个自动处理和 _log_info拼接
                _log_info += ss.str(); // 这就是一条完整的日志信息
                // 因为用户的信息最终是要输入到 LogMessage 所以 , 返回该对象
                return *this;
                // 不用引用返回 , 产生临时对象 , 这样是生命周期到了自动调用析构
                // 这样就会自动进行刷新
            }

            // 重载 << 以支持 std::endl 等操纵符
            // using Manipulator = std::ostream &(*)(std::ostream &);
            // LogMessage &operator<<(Manipulator manip)
            // {
            //     // 将操纵符应用到临时流，捕获其效果（如换行）
            //     manip(_manip_stream);
            //     // 提取临时流中的内容（如换行符）并追加到日志信息
            //     _log_info += _manip_stream.str();
            //     // 清空临时流，避免重复输出
            //     _manip_stream.str("");
            //     return *this;
            // }

        private:
            std::string _log_time;
            pid_t _pid;
            LogLevel &_log_level;
            int _line_number;
            std::string _src_filename;

            std::string _log_info;           // 日志信息 , 用户保存最后的一条完整信息
            Logger &_logger;                 // 包含外部类 , 访问外部类
            std::stringstream _manip_stream; // 支持 std::endl
        };

        // 使用仿函数 , 生成Logger 对象
        LogMessage operator()(LogLevel level, std::string name, int line)
        {
            // 直接调用 LogMessage定义对象

            return LogMessage(level, name, line, *this);
        }

    private:
        // 日志刷新策略 , 让用户选择
        // 这里必须是这个基类 , 这样才能实现多态
        // 基类的指针或引用是条件
        std::unique_ptr<LogStrategy> _fflush_strategy; // 刷新策略
    };

    // 用户使用用的是 Logger 类 ,  但是真正完成信息的是 LogMessage 类
    // 为了方便使用 , 这样做 : 用宏

    // 1.定义全局的 Logger
    Logger logger;

#define LOG(level) logger(level, __FILE__, __LINE__)                    // 宏函数
#define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy() // 宏函数
#define Enable_File_Log_Strategy() logger.EnableFileLogStrategy()       // 宏函数

}
